#include "../base/codec_info.h"
#include "../vgmstream.h"
#include "../coding/coding.h"
#include "../base/sbuf.h"

/*
 * DreamFactory ADPCM/DPCM Codecs
 *
 * Titles: Dust 1996 3.1/95 - Titanic: Adventure Out of Time
 * v4.0 Codec (decode_df_v40):
 * A custom 8-bit to 8-bit table-based ADPCM format. It uses two lookup tables
 * to determine the next sample value based on the current one. The compressed
 * stream contains control codes to switch between three decoding modes:
 *
 * - Mode I: Sets the next sample to a new absolute value.
 * - Mode II: Decodes a series of samples using the lookup tables.
 * - Mode III: Repeats the previous sample value for a number of times.
 *
 * Has a DC offset of 64, instead of 128.
 *
 * Titles: Disney's Math/Reading Quest with Aladdin
 * v4.1 Codec (decode_df_v41):
 * A simpler 8-bit to 16-bit DPCM format. The input byte determines whether
 * it represents a delta value to be added to the current sample or a new
 * absolute sample value, based on its highest bit.
 *
 */


/* DreamFactory v4.0 DPCM Decoder (8-bit to 8-bit) */

static const int8_t StepSizeTable[256] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06,
    0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,
    0xF8, 0xF8, 0xF8, 0xF8, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9,
    0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA,
    0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB,
    0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFC, 0xFC, 0xFC, 0xFC,
    0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFD, 0xFD,
    0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD,
    0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
    0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF
};

static const int8_t IndexTable[256] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD,
    0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB,
    0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9,
    0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
    0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03,
    0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01,
    0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD,
    0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB,
    0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9,
    0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
    0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03,
    0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x00, 0x01,
    0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD,
    0xFE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xF8, 0xF9, 0xFA, 0xFB,
    0xFC, 0xFD, 0xFE, 0xFF
};

/* DC offset fix. */
#define DF_V40_SAMPLE_TO_16BIT(sample) ((int16_t)(((int8_t)(sample) - 0x40) << 8))

static bool decode_cf_df_v40(VGMSTREAM* v, sbuf_t* sdst) {
    VGMSTREAMCHANNEL* stream = &v->ch[0];
    int samples_to_do = sdst->samples - sdst->filled;
    sample_t* outbuf = sbuf_get_filled_buf(sdst);
    int i;
    int8_t prev_sample = (int8_t)stream->adpcm_history1_16;

    if (stream->offset == stream->channel_start_offset) {
        prev_sample = read_u8(stream->offset, stream->streamfile);
        stream->offset++;
        if (samples_to_do > 0) {
            outbuf[0] = DF_V40_SAMPLE_TO_16BIT(prev_sample);
            samples_to_do--;
            outbuf++;
        }
    }

    for (i = 0; i < samples_to_do; ) {
        uint8_t control_byte = read_u8(stream->offset, stream->streamfile);
        stream->offset++;

        if ((control_byte & 0x80) == 0) {
            prev_sample = (int8_t)control_byte;
            outbuf[i++] = DF_V40_SAMPLE_TO_16BIT(prev_sample);
        } else if ((control_byte & 0x40) == 0) {
            int count = (control_byte & 0x3f) + 1;
            for (int j = 0; j < count && i < samples_to_do; j++) {
                uint8_t table_val = read_u8(stream->offset, stream->streamfile);
                stream->offset++;
                int8_t step_delta = StepSizeTable[table_val];
                int8_t index_delta = IndexTable[table_val];
                int8_t step_sample = prev_sample + step_delta;
                int8_t index_sample = step_sample + index_delta;
                outbuf[i++] = DF_V40_SAMPLE_TO_16BIT(step_sample);
                if (i >= samples_to_do) {
                    prev_sample = step_sample;
                    break;
                }
                outbuf[i++] = DF_V40_SAMPLE_TO_16BIT(index_sample);
                prev_sample = index_sample;
            }
        } else {
            int count = (control_byte & 0x3f) + 1;
            for (int j = 0; j < count && i < samples_to_do; j++) {
                outbuf[i++] = DF_V40_SAMPLE_TO_16BIT(prev_sample);
            }
        }
    }
    stream->adpcm_history1_16 = prev_sample;
    sdst->filled += (sdst->samples - sdst->filled) - samples_to_do + i;
    return true;
}

static bool decode_cf_df_v41(VGMSTREAM* v, sbuf_t* sdst) {
    VGMSTREAMCHANNEL* stream = &v->ch[0];
    int samples_to_do = sdst->samples - sdst->filled;
    sample_t* outbuf = sbuf_get_filled_buf(sdst);
    int16_t current_sample = stream->adpcm_history1_16;

    for (int i = 0; i < samples_to_do; i++) {
        uint8_t input_byte = read_u8(stream->offset, stream->streamfile);
        stream->offset++;
        if ((input_byte & 0x80) == 0) {
            int16_t delta = (input_byte << 9);
            delta >>= 4;
            current_sample += delta;
        } else {
            current_sample = (input_byte << 9);
        }
        outbuf[i] = current_sample;
    }
    stream->adpcm_history1_16 = current_sample;
    sdst->filled += samples_to_do;
    return true;
}

const codec_info_t cf_df_v40_decoder = {
    .decode_buf = decode_cf_df_v40,
};

const codec_info_t cf_df_v41_decoder = {
    .decode_buf = decode_cf_df_v41,
};
